using BigPondUsage.Properties;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Runtime.Remoting.Messaging;

namespace BigPondUsage
{
    /// <summary>
    /// Handles the SiteMinder login system that BigPond uses with a recursive algorithm. <br />May or may not be reliable.
    /// </summary>
    [ComVisible(true)]
    internal class BigPondSMLogin
    {
        /// <summary>
        /// The URL to access the My Usage page.
        /// </summary>
        internal const string myUsageURL = "https://my.bigpond.com/mybigpond/myaccount/myusage/default.do";

        /// <summary>
        /// The URL to access the Monthly Usage page.
        /// </summary>
        internal const string monthlyURL = "https://my.bigpond.com/mybigpond/myaccount/myusage/monthly/default.do";

        /// <summary>
        /// The URL to access the Daily Usage page.
        /// </summary>
        internal const string dailyURL = "https://my.bigpond.com/mybigpond/myaccount/myusage/daily/default.do";

        /// <summary>
        /// The first URL to start the authentication process. <br />This is the URL where username and password details are POSTed to.
        /// New System
        /// </summary>
        private const string postNewURL = "https://signon.bigpond.com/login";

        /// <summary>
        /// Holds the HTML of the retrieved page
        /// </summary>
        private string html = "";

        /// <summary>
        /// The username of the account. <br />Passed from Usage.
        /// </summary>
        private string username = "";

        /// <summary>
        /// The password of the account. <br />Passed from Usage.
        /// </summary>
        private string password = "";

        /// <summary>
        /// The next location to jump to given by the Location HTTP header or a META redirect.
        /// </summary>
        private string nextLocation = "";
        
        /// <summary>
        /// The number of times the recursive login method has been called.
        /// </summary>
        private int recursiveCount = 0;

        /// <summary>
        /// WebProxy object to use when doing the SMLogin.
        /// </summary>
        public WebProxy proxy = null;

        /// <summary>
        /// Flag to enable and disable the use of the proxy.
        /// </summary>
        public bool useProxy = false;

        /// <summary>
        /// Holds the cookies for the next request
        /// </summary>
        private CookieContainer cc = null;

        /// <summary>
        /// Last run time.
        /// </summary>
        private DateTime lastRunTime;

        /// <summary>
        /// Has been run for first time?
        /// </summary>
        private bool run = false;

        private int num = 0;

        /// <summary>
        /// The default constructor for BigPondSMLogin. <br />
        /// Initialises a new instance of BigPondUsage.BigPondSMLogin to handle the SiteMinder login system.
        /// </summary>
        /// <param name="username">The username of the account. <br />Make sure to use the full e-mail address. <br />Passed from Usage.</param>
        /// <param name="password">The password of the account. <br />Passed from Usage.</param>
        public BigPondSMLogin(string username, string password)
        {
            this.username = username;
            this.password = password;
        }

        /// <summary>
        /// Begins the authentication process and returns the HTML for the 3 main pages using the recursive algorithm and browseTo. <br />
        /// 0-My Usage 1-Monthly Usage 2-Daily Usage
        /// </summary>
        /// <returns>The HTML for My Usage, Monthly Usage and Daily Usage in a string array.</returns>
        public string[] getPages()
        {
            string[] pages = new string[3];
            pages[0] = browseTo(myUsageURL);
            pages[1] = browseTo(monthlyURL);
            pages[2] = browseTo(dailyURL);
            run = true;

            return pages;
        }

        /// <summary>
        /// A recursive algorithm to handle the SiteMinder BigPond login process. <br /><br />
        /// 
        /// Does the POST to the login URL first then for each location: <br />
        /// - The Cookies are set for the request. <br />
        /// - The Referer is set for the request. <br />
        /// - The Set-Cookie header is parsed from the response and stored for the next request. <br />
        /// - The Location Header or META redirect information is set as the nextLocation. <br /><br />
        /// 
        /// Once there is no Location header or META redirect, the page is read in and returned
        /// </summary>
        /// <param name="gotoURL">The page to get</param>
        /// <returns>The page contents for specificed URL</returns>
        /// <exception cref="UserNamePasswordException">If the username or password is incorrect or the account has been locked out.</exception>
        private string recursiveLogin(string gotoURL)
        {
            DateTime currTime = DateTime.Now;
            TimeSpan duration = currTime - lastRunTime;

            if (duration.TotalSeconds >= 90)
                run = false;
            if (!run)
                lastRunTime = DateTime.Now;

            if (recursiveCount == 0 && run && duration.TotalSeconds < 90)
                    nextLocation = gotoURL;
 
            if (recursiveCount == 0 && !run)
            {
                #region Do first POST to begin authentication sequence.
                // Mono compatibility
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(postNewURL);
                req.Method = "POST";
                if (useProxy && proxy != null)
                    req.Proxy = proxy;
                req.AllowAutoRedirect = false;
                // Turns off the Expect-100: Continue header that .NET likes to add when POSTing
                ServicePointManager.Expect100Continue = false;

                #region Check @bigpond.com
                username = username.ToLower(new CultureInfo(""));
                username = username.Replace("@bigpond.com", "");
                #endregion

                string postData = "goto=" + Utils.escape(gotoURL) + "&gotoOnFail=&encoded=false&gx_charset=UTF-8&username=" + Utils.escape(username) + "&password=" + Utils.escape(password);
             
                req.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7 (.NET CLR 3.5.30729)";
                req.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
                req.Headers.Add(HttpRequestHeader.AcceptLanguage, "en-us,en;q=0.5");
                req.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
                req.Headers.Add(HttpRequestHeader.AcceptCharset, "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
                req.Referer = "https://signon.bigpond.com/login";

                byte[] byteArray = Encoding.UTF8.GetBytes(postData);
                req.ContentType = "application/x-www-form-urlencoded";
                req.ContentLength = byteArray.Length;
                Stream dataStream = req.GetRequestStream();
                dataStream.Write(byteArray, 0, byteArray.Length);
                dataStream.Dispose();

                num++;

                foreach (string s in req.Headers)
                {
                    Console.Write(s + ": ");
                    Console.WriteLine(req.Headers.Get(s));
                }

                HttpWebResponse res = (HttpWebResponse)req.GetResponse();

                #region Set cookies from response for next request.
                cc = new CookieContainer();
                if (!String.IsNullOrEmpty(res.Headers.Get("Set-Cookie")))
                {
                    List<string> domains = new List<string>();
                    domains.Add(res.ResponseUri.Host);
                    
                    Collection<string> domainsSetCookie = Utils.substringArray(res.Headers.Get("Set-Cookie"), "Domain=", ";");
                    foreach (string s in domainsSetCookie)
                    {
                        if (!s.Equals("", StringComparison.CurrentCulture))
                            domains.Add(s);
                    }
                    
                    if (!String.IsNullOrEmpty(res.Headers.Get("Location"))) 
                    {
                        domains.Add(new Uri(res.Headers.Get("Location")).Host);
                    }

                    foreach (string s in domains)
                    {
                        try
                        {
                            cc.SetCookies(new Uri("http://" + s), res.Headers.Get("Set-Cookie"));
                        }
                        catch (UriFormatException) { }
                        catch (CookieException) { }
                    }
                }
                #endregion

                nextLocation = res.Headers.Get("Location");
                
                res.Close();
                recursiveCount++;
                run = true;
                recursiveLogin(gotoURL);
                #endregion
            }
            else if (recursiveCount < 30)
            {
                #region Everything after POST
                if (nextLocation.StartsWith("/", StringComparison.CurrentCulture))
                    throw new UsageDownloadException(Resources.ResourceManager.GetString("UsageDownloadExceptionString3"));

                if (nextLocation.Contains("error"))
                    throw new UsageDownloadException(Resources.ResourceManager.GetString("UserNamePasswordExceptionString1"));

                // Mono compatibility
                ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(nextLocation);
                if (useProxy && proxy != null)
                    req.Proxy = proxy;
                req.AllowAutoRedirect = false;

                #region Set required cookies for request.
                req.CookieContainer = cc;
                #endregion

                num++;
                HttpWebResponse res = (HttpWebResponse)req.GetResponse();

                #region Set cookies from response for next request.
                if (!String.IsNullOrEmpty(res.Headers.Get("Set-Cookie")))
                {
                    List<string> domains = new List<string>();
                    domains.Add(res.ResponseUri.Host);

                    Collection<string> domainsSetCookie = Utils.substringArray(res.Headers.Get("Set-Cookie"), "Domain=", ";");
                    foreach (string s in domainsSetCookie)
                    {
                        if (!s.Equals("", StringComparison.CurrentCulture))
                            domains.Add(s);
                    }

                    if (!String.IsNullOrEmpty(res.Headers.Get("Location")))
                    {
                        domains.Add(new Uri(res.Headers.Get("Location")).Host);
                    }

                    foreach (string s in domains)
                    {
                        try
                        {
                            cc.SetCookies(new Uri("http://" + s), res.Headers.Get("Set-Cookie"));
                        }
                        catch (UriFormatException) { }
                        catch (CookieException) { }
                    }
                }
                #endregion


                #region Set next location by handling the Location header, META redirect or the final page.
                if (String.IsNullOrEmpty(res.Headers.Get("Location")))
                {
                    StreamReader sr = new StreamReader(res.GetResponseStream());
                    string metaRedirect = sr.ReadToEnd();
                    if (!String.IsNullOrEmpty(Utils.substring(metaRedirect, "<meta name=\"Description\" content=\"", " - "))) // Found the usage page
                    {
                        html = metaRedirect;
                        recursiveCount = 100;
                    }
                    else if (!String.IsNullOrEmpty(Utils.substring(metaRedirect, "URL=", "\" />")))
                        nextLocation = Utils.substring(metaRedirect, "URL=", "\" />");
                    else if (!String.IsNullOrEmpty(Utils.substring(metaRedirect, "URL=", "\">")))
                        nextLocation = Utils.substring(metaRedirect, "URL=", "\">");
                    else
                    {
                        html = metaRedirect;
                        recursiveCount = 100;
                    }
                    sr.Dispose();
                }
                else
                    nextLocation = res.Headers.Get("Location");
                #endregion

                res.Close();
                recursiveLogin(gotoURL);
                #endregion
            }

            return html;
        }

        /// <summary>
        /// Browse to another page, returning the HTML.
        /// </summary>
        /// <param name="url">URL to go to</param>
        /// <returns>The new page HTML.</returns>
        public string browseTo(string url)
        {
            recursiveCount = 0;
            return recursiveLogin(url);
        }
    }
}
